package client;

/**
 * Describe class MainServer here.
 *
 *
 * Created: Wed Feb 10 18:27:34 2010
 * Modified : Mon Apr 25 17:35:26 2011
 *
 * @author <a href="mailto:francois@ubuntu">FM</a>
 * @version 1.0
 */

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.nio.channels.ClosedByInterruptException;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.SimpleFormatter;

import common.HtmlFormatter;

import serveur.AnnonceurServerDelegate;

public final class MainClient {

    /**
     * Creates a new <code>MainServer</code> instance.
     *
     */
	// VALEURS PAR DEFAUT
    static int NB_THREADS = 20;
    final static int PORT_BASE_DEFAUT = 3000;
    static int userServiceCpt;
    static int annonceurServiceCpt;

    static private String agentId="1234";
    static private String agentVersion="0.0";
    static private String agentMS="Master";

    private static Logger logger = Logger.getLogger(MainClient.class.getName());

	private static final String LOG_LEVEL_KEY = "log.filterLevel";		
	private static final String LOG_FORMATTERS_KEY = "log.formatters";

    private static ExecutorService pool;
    private boolean mClientOn;
    private static Level  logLevel;
	
	private static FileHandler logFileTxt;
	private static FileHandler logFileHTML;
	
	private static Properties conf = new Properties();
	
	private static final String LOG_FILE_PREFIX_NAME = "client-output";

    private boolean mbModeAdmin;
    
    private CanalUserHandler mCanalUser;

    private MonitorConnexion mMonitor = new MonitorConnexion();

    
    /**
     * CONSTRUCTEUR
     * @param poolsize
     * @param portbase
     */
    private MainClient(int poolsize,String IPAdresse, int portbase, boolean bModeAdmin) {
    	mbModeAdmin = bModeAdmin;
    	mClientOn = true;
    	pool = Executors.newFixedThreadPool(poolsize);
    	if ( bModeAdmin == true ){
    		portbase = portbase+2;
    	}
    	mCanalUser = new CanalUserHandler(IPAdresse, portbase, pool);
    	mMonitor.start();
    	mCanalUser.start();
    	
    }

    /**
     * METHODES
     */
    void halt(){
    	pool.shutdown();
    	try{
    	    pool.awaitTermination(60,TimeUnit.SECONDS);
    	}catch(InterruptedException ex){
    		logger.log(Level.SEVERE,"Unexpected error",ex);
    	}
    	mCanalUser.interrupt();
    	//mCanalAnnonceur.interrupt();
    	mClientOn = false;
    }

    boolean isClientOn(){
    	return mClientOn;
    }

        
    /**
     * GETTERS & SETTERS
     */
    static String getMasterOrSlave(){
    	return agentMS;
    }

    static String getId(){
    	return agentId;
    }

    static String getVersion(){
    	return agentVersion;
    }

    static Level getLogLevel(){
       	if (logLevel == null) {
       		String level = conf.getProperty(LOG_LEVEL_KEY,"INFO").toUpperCase().trim();
       		logLevel = Level.parse(level);
       	}
    	return logLevel;
    }
    
    static void initLogger(){
        logger.setLevel(getLogLevel());
        String logFormatters = conf.getProperty(LOG_FORMATTERS_KEY,"HTML,TXT,CONSOLE");
        StringTokenizer st = new StringTokenizer(logFormatters,",; ");
        String formatter;
        while (st.hasMoreTokens()) {
        	formatter = st.nextToken().toUpperCase().trim();
        	if (formatter.equals("HTML")) {
        		try {
        			logFileHTML = new FileHandler(LOG_FILE_PREFIX_NAME+".html");
            		logFileHTML.setFormatter(new HtmlFormatter());
            		logger.addHandler(logFileHTML);  
        		} catch (Exception ex) {
        			logger.log(Level.SEVERE, "Unable to create log file: "+LOG_FILE_PREFIX_NAME+".html", ex);
        		} 
        	}
        	if (formatter.equals("TXT")) {
        		try {
        			logFileTxt = new FileHandler(LOG_FILE_PREFIX_NAME+".txt");
            		logFileTxt.setFormatter(new SimpleFormatter());
            		logger.addHandler(logFileTxt);  
        		} catch (Exception ex) {
        			logger.log(Level.SEVERE, "Unable to create log file: "+LOG_FILE_PREFIX_NAME+".txt", ex);
        		}      		
        	}
        }
	}
	
	
	//////////////////////////////////////
	//									//
	//		CLASSES INTERNES			//
	//									//
	//////////////////////////////////////
	/**
	 * MonitorConnexion
	 * 
	 */
	public class MonitorConnexion extends Thread {
		boolean activ;
		int cptcour;
		int cptpre;
		private int debitCanalUser;
		
		MonitorConnexion(){
		    activ = true;
		}
		
		MonitorConnexion(int canalUserCpt){
		    activ = true;
		    
		}
		
		public void run(){
		    while (activ){
		    	cptpre = cptcour;
		    	cptcour = mCanalUser.getCpt();
//		    	cptcour = MainServer.getCanalUser().getCpt();
		    	debitCanalUser = (cptcour-cptpre);
		    	//logger.log(Level.INFO,"connexion user " + debitCanalUser + "/s");
		    	try{
		    		sleep(1000);
		    	} catch (InterruptedException ex){
		    		logger.log(Level.SEVERE,"Unexpected error",ex);
		    	}
		    }
		}
		
		void terminate(){
		    activ = false;
		}
		
		int getDebitCanalUser() {
			return debitCanalUser;
		}
	}
	
	
	/**
	 * CanalUserHandler
	 * Port de base 3000 -  Gere les connexions et les echanges de messages
	 */
	class CanalUserHandler  extends Thread{
		
		private SocketAddress m_SocketAddress;
		private InetAddress m_IpAddress;
		private Socket m_Socket;
		private final ExecutorService mPool;
		private int mCpt = 0;
		private UserClientDelegate mUserClient;
		
		CanalUserHandler(String IPAdresse, int port, ExecutorService pool){
		    this.mPool = pool;
		    m_Socket = new Socket();
			try {
				m_IpAddress = InetAddress.getByName(IPAdresse);
				m_SocketAddress = new InetSocketAddress(m_IpAddress,port);
				m_Socket.connect(m_SocketAddress, 30);
				System.out.println("connect");
			} catch (IOException ex) {
				logger.log(Level.SEVERE,"Unexpected error",ex);
			}
		}

		public InetAddress getM_IpAddress() {
			return m_IpAddress;
		}

		public void run(){
			mUserClient = new UserClientDelegate(m_IpAddress,m_Socket,mCpt++, mbModeAdmin, MainClient.this ,mPool);
			// incremente le nombre d'utilisateurs connectes
			MainClient.userServiceCpt++;
		    while (isClientOn()) {
		    	try {
					sleep(10);
					mUserClient.run();
					mClientOn = !mUserClient.isStop();
				} catch (InterruptedException ex) {
					logger.log(Level.SEVERE,"Unexpected error",ex);
				}
			}
		}
		
		int getCpt(){
		    return mCpt;
		}
	}

	
	/**
	 * CanalAnnonceurHandler
	 * Gere les transfert de données avec le controle de flux
	 */
	class CanalAnnonceurHandler  extends Thread{

		private SocketAddress m_SocketAddress;
		private InetAddress m_IpAddress;
		private Socket m_Socket;
		private final ExecutorService mPool;
		private int mCpt = 0;
		private AnnonceurClientDelegate mAnnonceurClientDelegate; 
		

		CanalAnnonceurHandler(String IPAdresse, int port, ExecutorService pool){
		    this.m_Socket = new Socket();
		    this.mPool = pool;
		    
		    try {
		    	this.m_IpAddress = InetAddress.getByName(IPAdresse);
		    	this.m_SocketAddress = new InetSocketAddress(this.m_IpAddress,port);
		    	this.m_Socket.connect(this.m_SocketAddress, 30);
				logger.log(Level.FINE, "connect CanalAnnonceurHandler");
				}catch(IOException ex){
					logger.log(Level.SEVERE,"Unexpected error",ex);
		    }
		}

		public boolean createAnnonceurClientDelegate(){
			MainClient.userServiceCpt++;
			logger.log(Level.FINE, m_Socket.toString());
			mAnnonceurClientDelegate = new AnnonceurClientDelegate(m_Socket,mCpt++);
			logger.log(Level.FINE, "mAnnonceurClientDelegate = new AnnonceurClientDelegate(m_Socket,mCpt++);");
			return true;
		}
		
		public void run(){
		    while (isClientOn()) {
		    	mPool.execute(mAnnonceurClientDelegate);
			}
		}

		public AnnonceurClientDelegate getAnnonceurClientDelegate() {
			logger.log(Level.FINE, "getAnnonceurClientDelegate()");
			return mAnnonceurClientDelegate;
		}
	}
	
	static void affichePrompt(){
		System.out.print("> ");
	}
	
	static void mainMenu(){
		System.out.println("*****************************************");
		System.out.println("* Client FTP Java JoStef");
		System.out.println("* Choix du mode de connexion :");
		System.out.println("* Entrer 1 pour Ourvrir le service en mode user");
		System.out.println("* Entrer 2 pour Ourvrir le service en mode Admin du server");
		System.out.println("*****************************************");
		affichePrompt();
		
	}
	
	
	static int lectureClavierMainMenu(){
		return client.Clavier.lireInt();
	}
	
	//////////////////////////////////////
	//									//
	//				MAIN				//
	//									//
	//////////////////////////////////////
	/**
     *
     * Describe <code>main</code> method here.
     *
     * @param args a <code>String</code> value
     */
    public static void main(final String[] args) {
    	String IPAdresse = "127.0.0.1";
    	int portBase = PORT_BASE_DEFAUT;
    	
    	// Chargement du fichier de configuration
        // Initialise les configurations du serveur a partir d'un fichier de configuration
   		InputStream is;
   		InputStreamReader isr;
   		BufferedReader br;
   		String ligne;
   		String fileConf = "client.conf";
    				
   		// Test de la fonction execute
   		logger.log(Level.INFO, "Chargement de la configuration du serveur à paritr du fichier : "+fileConf);
    		
   		// Lecture du fichier d'authentification
   		try {
   			initLogger();
   			// creation du flux de fichier
   			is = new FileInputStream(fileConf);
   			isr = new InputStreamReader(is);
   			br = new BufferedReader(isr);

   			// recuperation de la configuration
   			while ((ligne=br.readLine()) != null){
   				String[] temp = ligne.split("=");
   				// ADRESSE IP
   				if((temp[0].equals("address")) && (!temp[1].isEmpty())){
   					// Test si le parametre est bien une adresse IP
   					if(temp[1].matches("[0-9]{1,3}(?:.[0-9]{1,3}){3}")){
   						IPAdresse = temp[1];
   						logger.log(Level.INFO, "IP ADDRESS = "+IPAdresse);
   					} else {
   						logger.log(Level.WARNING, "ADDRESS INCORRECT VALUE (DEFAUT = "+IPAdresse+")");
   					}
   				}
   				// PORT
   				if((temp[0].equals("port")) && (!temp[1].isEmpty())){
   					// Test si le parametre est un entier puis si le port est compris entre 0 et 65535
   					if((Integer.valueOf(temp[1])>=0) && (Integer.valueOf(temp[1])<=65535)){
   						portBase = Integer.valueOf(temp[1]);
   						logger.log(Level.INFO, "PORT = "+portBase);
   					} else {
   						logger.log(Level.WARNING, "PORT INCORRECT VALUE (DEFAUT = "+portBase+")");
   					}
   				}
   				// NB_THREADS
   				if((temp[0].equals("nbThreads")) && (!temp[1].isEmpty())){
   					// Test si le parametre est un entier puis si le nombre est compris entre 20 et 100
   					if((Integer.valueOf(temp[1])>=20) && (Integer.valueOf(temp[1])<=100)){
   						NB_THREADS = Integer.valueOf(temp[1]);
   						logger.log(Level.INFO, "NB_THREADS = "+NB_THREADS);
   					} else {
   						logger.log(Level.WARNING, "NB_THREADS INCORRECT VALUE (DEFAUT = "+NB_THREADS+")");
   					}
   				}   				
   				// agentID
   				if((temp[0].equals("agentID")) && (!temp[1].isEmpty())){
					agentId = temp[1];
					logger.log(Level.INFO, "agentId = "+agentId);
   				}
   				// agentVersion
   				if((temp[0].equals("agentVersion")) && (!temp[1].isEmpty())){
					agentVersion = temp[1];
					logger.log(Level.INFO, "agentVersion = "+agentVersion);
   				}
   				// agentMS
   				if((temp[0].equals("agentMS")) && (!temp[1].isEmpty())){
					agentMS = temp[1];
					logger.log(Level.INFO, "agentMS = "+agentMS);
   				}
   			}
   			// Fermeture du flux
   			is.close();
    				
   		} catch (FileNotFoundException ex) {
   			logger.log(Level.SEVERE,"File not found",ex);
   		} catch (IOException ex) {
   			logger.log(Level.SEVERE,"Unexpected error",ex);
   		}

   		// Prise en compte de l'adresse IP et du port passé en parametre au demarrage du serveur
    	if (args.length > 0){
    		try{
    			if (args[0].matches("[0-9]{1,3}(?:.[0-9]{1,3}){3}")){
    				IPAdresse = args[0];
    				logger.log(Level.INFO, "IP ADDRESS BY COMMAND LINE = "+IPAdresse);
    			}
    			if ((Integer.parseInt(args[1])>=0) && (Integer.parseInt(args[1])<=65535)){
    				portBase = Integer.parseInt(args[1]);
    				logger.log(Level.INFO, "PORT BY COMMAND LINE = "+portBase);
    			}
    		}catch(Exception ex){
    		logger.severe("Connection interrupted");
    		}
    	}

    	boolean bModeAdmin = false;
    	MainClient.mainMenu();
    	
    	switch ( MainClient.lectureClavierMainMenu() )
		{
		case 1:
			bModeAdmin =false;
			break;
		case 2:
			bModeAdmin = true;
			break;
		}
    	
    	try{
    		MainClient MainClient = new MainClient(NB_THREADS,IPAdresse, portBase, bModeAdmin );
    		MainClient.mCanalUser.join();	// Attend la fin du thread canalUser
    		MainClient.mMonitor.terminate();
    		}
    	catch (Exception ex)
    		{
            logger.severe("Connection interrupted");
        } finally {
        }    
        logger.log(Level.INFO,"Diffuser server halted\n");
    }
}
    